# _*_ coding:utf-8 _*_
# 开发团队: Mavericks
# 开发人员：Maverick·Ming
# 开发IDE： PyCharm
#   项目名：EchartsSpider
#   文件名：spider.py
# 开发时间：2021/4/3 1:07


# 引入所需模块
import os
import re  # 网页解析，获取数据
import sqlite3  # 进行SQLite数据库操作
import urllib.error  # 制定URL，获取网页数据
import urllib.request

import xlwt  # 进行excel操作
from bs4 import BeautifulSoup  # 正则表达式，进行文字匹配


def main():  # 整体工作流程
    print('hello！开始爬取...')
    baseurl = 'https://movie.douban.com/top250?start='
    save_path = 'data/豆瓣电影Top250.xls'
    save_db_path = 'movie250.db'
    # 获取数据
    datalist = get_data(baseurl)
    # 保存数据
    # save_data(datalist, save_path)
    # print('Haha! 爬取完成，请查看xls文件')
    save_data_db(datalist, save_db_path)
    print('Haha! 爬取完成，请查看movie250.db文件')


# 影片详情链接的规则
findLink = re.compile(r'<a href="(.*?)">')  # 创建 正则表达式/字符串模式/规则 对象
# 影片的图片
findImgSrc = re.compile(r'<img.*src="(.*?)"', re.S)  # re.S忽略标签中的换行符,让换行符包含在解析字符中
# 影片片名
findTitle = re.compile(r'<span class="title">(.*?)</span>')  # r字符串原始格式,避免被转义字符干扰
# 影片评分
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*?)</span>')
# 评价人数
findJudge = re.compile(r'<span>(\d*)人评价</span>')  # /d表示一个数字
# 影片概况
findInq = re.compile(r'<span class="inq">(.*?)</span>')  # 简要的概况应该不会有换行,有再加re.S
# 影片导演年类等基础信息
findBd = re.compile(r'<p class="">(.*?)</p>', re.S)


# 定义爬取并解析数据的函数
def get_data(baseurl):
    datalist = []  # 保存解析后的数据
    for i in range(0, 10):  # 调用获取页面信息的函数，10次
        url = baseurl + str(i * 25)  # url封装
        # 1.爬取网页源码
        html = ask_url(url)  # 保存获取一个网页的源码
        # 2.逐一解析数据
        soup = BeautifulSoup(html, 'html.parser')  # html.parser解析器,生成一个文档树
        for item in soup.find_all('div', class_="item"):  # 查找符合要求的字符串,保存成列表
            data = []  # 保存一个电影item的所有信息[list]
            item = str(item)

            # 编写好所有规则之后,开始找内容,存内容
            movie_link = re.findall(findLink, item)[0]  # 调用re.findall(利用规则, 在item文档树里解析信息)
            data.append(movie_link)  # 添加链接

            img_src = re.findall(findImgSrc, item)[0]
            data.append(img_src)  # 添加图片

            titles = re.findall(findTitle, item)  # 可能只有一个中文名,没有外国名
            if len(titles) == 2:
                chinese_title = titles[0]
                data.append(chinese_title)  # 添加中文名
                foreign_title = titles[1].replace('/', '')  # 去除无关字符
                foreign_title = foreign_title.replace(u'\xa0', '')  # 去除扩展字符集
                data.append(foreign_title)  # 添加外国名
            else:
                data.append(titles[0])  # 添加中文名
                data.append(' ')  # 外国名留空

            rating = re.findall(findRating, item)[0]
            data.append(rating)  # 添加评分

            judge = re.findall(findJudge, item)[0]
            data.append(judge)  # 添加评论人数

            inq = re.findall(findInq, item)  # 概述[可能没有概述]
            if len(inq) != 0:
                inq = inq[0].replace('。', '')
                data.append(inq)  # 添加概述
            else:
                data.append(' ')  # 没有概述就留空

            bd = re.findall(findBd, item)[0]
            bd = re.sub('<br(\s+)?/>(\s+)?', ' ', bd)  # 替换<br/>为空格
            bd = re.sub('/', ' ', bd)  # 替换/
            bd = bd.replace(u'\xa0', ' ')  # 去除\xa0
            # 删除文字前后空格后,传给data
            data.append(bd.strip())  # 添加基础信息

            # 以上已经把item的信息处理出来存在data了
            datalist.append(data)  # 把处理好的信息放入datalist

    return datalist


# 得到一个指定url的网页信息内容（只获取其中一个页面的爬取，但是需要250/25=10个页面，就需要些循环调用）
def ask_url(url):
    head = {  # 模拟浏览器头部信息，向豆瓣服务器发送消息
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
    }  # head里的变量值User-Agent用户代理,告诉服务器，我们是浏览器访问，不是爬虫
    # 接着将请求对象封装起来
    req = urllib.request.Request(url, headers=head)
    html = ""
    # 发送请求，并接受返回源码，可能会出现异常，为了捕获异常用上try语句
    try:
        response = urllib.request.urlopen(req)  # respone对象接受返回的网页信息
        html = response.read().decode('utf-8')  # 将网页信息中的html信息解码给html
        # print(html)
    except urllib.error.URLError as e:
        if hasattr(e, 'code'):
            print(e.code)
        if hasattr(e, 'reason'):
            print(e.reason)
    finally:
        return html


# 定义保存数据的函数
def save_data(in_data, in_path):
    workbook = xlwt.Workbook(encoding='utf-8', style_compression=0)  # 样式压缩效果
    worksheet = workbook.add_sheet('top250', cell_overwrite_ok=True)  # 可覆盖工作表

    # 表首行的列名定义
    col = ('电影详情链接', '图片链接', '中文片名', '英文片名', '评分', '评论数', '概况', '相关信息')
    for i in range(0, 8):
        worksheet.write(0, i, col[i])  # 列名

    # 保存电影信息
    for i in range(0, 250):
        print('第%d条' % (i + 1))
        data = in_data[i]
        for j in range(0, 8):
            worksheet.write(i + 1, j, data[j])  #

    workbook.save(in_path)  # 保存数据


# 保存数据到SQLite
def save_data_db(datalist, path):
    init_db(path)
    # 插入数据
    conn = sqlite3.connect(path)
    cur = conn.cursor()
    for data in datalist:  # datalist是二重列表,列表中嵌套列表
        for index in range(len(data)):  # 操作子列表
            if index == 4 or index == 5:  # 这两个index所在的位置存的是数值内容,所以不需要加""
                continue  # 因为有id自动生成,所以index因该是4,5
            data[index] = '"' + data[index] + '"'  # 在内容前后加上""
        sql = '''
            insert into movie(
            movie_link, img_link, chinese_name, foreign_name, 
            score, commented, summarize, base_data) VALUES (%s)''' % ",".join(data)
        cur.execute(sql)
        conn.commit()
    cur.close()
    conn.close()


# 初始化数据库,创建数据库
def init_db(path):
    os.remove(path)
    conn = sqlite3.connect(path)  # 连接数据库
    c = conn.cursor()  # 获取操作数据库的游标
    # 创建数据库
    sql = '''
            create table movie(
                id integer primary key autoincrement,
                movie_link text ,
                img_link text,
                chinese_name text,
                foreign_name text,
                score numeric, 
                commented numeric,
                summarize text,
                base_data text
            );
        '''
    c.execute(sql)  # 创建数据库
    conn.commit()
    conn.close()


# 程序入口，确保程序执行的流程
if __name__ == '__main__':  # 当程序被调用执行时, 按照此语句入口的顺序执行程序，不至于乱序调用
    # 调用函数
    main()
